/*
 * Copyright (c) 2012 Jason Polites
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bupt.library.pinch2zoom;

import android.content.res.Configuration;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;


public class GestureImageViewTouchListener implements OnTouchListener {

    public static String TAG = GestureImageViewTouchListener.class.getSimpleName();
    private final PointF current = new PointF();
    private final PointF last = new PointF();
    private final PointF next = new PointF();
    private final PointF midpoint = new PointF();
    private final VectorF scaleVector = new VectorF();
    private final VectorF pinchVector = new VectorF();
    private GestureImageView image;
    private OnClickListener onClickListener;
    private View.OnLongClickListener onLongClickListener;
    private boolean touched = false;
    private boolean inZoom = false;

    private float initialDistance;
    private float lastScale = 1.0f;
    private float currentScale = 1.0f;

    private float boundaryLeft = 0;
    private float boundaryTop = 0;
    private float boundaryRight = 0;
    private float boundaryBottom = 0;

    private float maxScale = 5.0f;
    private float midScale = 1.75f;
    private float originScale = 1f;
    private float minScale = 0.25f;
    private float fitScaleHorizontal = 1.0f;
    private float fitScaleVertical = 1.0f;

    public static final int defaultZoom = 0;
    public static final int threeLevelZoom= 1;
    private int zoomType = defaultZoom;

    private int canvasWidth = 0;
    private int canvasHeight = 0;

    private float centerX = 0;
    private float centerY = 0;

    private float resetToOriginX = 0;
    private float resetToOriginY = 0;

    private float startingScale = 0;

    private boolean canDragX = false;
    private boolean canDragY = false;

    private boolean multiTouch = false;

    private int displayWidth;
    private int displayHeight;

    private int imageWidth;
    private int imageHeight;

    private FlingListener flingListener;
    private FlingAnimation flingAnimation;
    private ZoomAnimation zoomAnimation;
    private MoveAnimation moveAnimation;
    private GestureDetector tapDetector;
    private GestureDetector flingDetector;
    private GestureImageViewListener imageListener;

    public GestureImageViewTouchListener(final GestureImageView image, int displayWidth, int displayHeight) {
        super();

        this.image = image;

        this.displayWidth = displayWidth;
        this.displayHeight = displayHeight;

        this.centerX = (float) displayWidth / 2.0f;
        this.centerY = (float) displayHeight / 2.0f;

        this.imageWidth = image.getImageWidth();
        this.imageHeight = image.getImageHeight();

        startingScale = image.getScale();

        currentScale = startingScale;
        lastScale = startingScale;

        boundaryRight = displayWidth;
        boundaryBottom = displayHeight;
        boundaryLeft = 0;
        boundaryTop = 0;

        next.x = image.getImageX();
        next.y = image.getImageY();

        flingListener = new FlingListener();
        flingAnimation = new FlingAnimation();
        zoomAnimation = new ZoomAnimation();
        moveAnimation = new MoveAnimation();

        flingAnimation.setListener(new FlingAnimationListener() {
            @Override
            public void onMove(float x, float y) {
                handleDrag(current.x + x, current.y + y);
            }

            @Override
            public void onComplete() {
            }
        });

        zoomAnimation.setZoom(2.0f);
        zoomAnimation.setZoomAnimationListener(new ZoomAnimationListener() {
            @Override
            public void onZoom(float scale, float x, float y) {
                if (scale <= maxScale && scale >= minScale) {
                    handleScale(scale, x, y);
                }
            }

            @Override
            public void onComplete() {
                inZoom = false;

                handleUp();
            }
        });

        moveAnimation.setMoveAnimationListener(new MoveAnimationListener() {

            @Override
            public void onMove(float x, float y) {
                image.setPosition(x, y);
                image.redraw();
            }
        });

        tapDetector = new GestureDetector(image.getContext(), new SimpleOnGestureListener() {
            @Override
            public boolean onDoubleTap(MotionEvent e) {

                switch (zoomType) {
                    case threeLevelZoom:
                        startThreeLevelZoom(e);
                        break;
                    default:
                        startDefaultZoom(e);
                }
                //防止双击后触发长按
                tapDetector.setIsLongpressEnabled(false);
                return true;
            }

            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                if (!inZoom) {
                    if (onClickListener != null) {
                        onClickListener.onClick(image);
                        return true;
                    }
                }

                return false;
            }

            @Override
            public void onLongPress(MotionEvent e) {
                if (!inZoom) {
                    if (onLongClickListener != null) {
                        onLongClickListener.onLongClick(image);
                    }
                }
            }
        });

        flingDetector = new GestureDetector(image.getContext(), flingListener);
        imageListener = image.getGestureImageViewListener();

        calculateBoundaries();
    }

    private void startFling() {
        flingAnimation.setVelocityX(flingListener.getVelocityX());
        flingAnimation.setVelocityY(flingListener.getVelocityY());
        image.animationStart(flingAnimation);
    }

    private void startDefaultZoom(MotionEvent e) {
        inZoom = true;
        zoomAnimation.reset();

        float zoomTo = 1.0f;

        if (image.isLandscape()) {
            if (image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
                int scaledHeight = image.getScaledHeight();

                if (scaledHeight < canvasHeight) {
                    zoomTo = fitScaleVertical / currentScale;
                    zoomAnimation.setTouchX(e.getX());
                    zoomAnimation.setTouchY(image.getCenterY());
                } else {
                    zoomTo = fitScaleHorizontal / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(image.getCenterY());
                }
            } else {
                int scaledWidth = image.getScaledWidth();

                if (scaledWidth == canvasWidth) {
                    zoomTo = currentScale * 4.0f;
                    zoomAnimation.setTouchX(e.getX());
                    zoomAnimation.setTouchY(e.getY());
                } else if (scaledWidth < canvasWidth) {
                    zoomTo = fitScaleHorizontal / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(e.getY());
                } else {

                    zoomTo = Math.min(fitScaleHorizontal, fitScaleVertical) / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(image.getCenterY());
                }
            }
        } else {
            if (image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {

                int scaledHeight = image.getScaledHeight();

                if (scaledHeight == canvasHeight) {
                    zoomTo = currentScale * 4.0f;
                    zoomAnimation.setTouchX(e.getX());
                    zoomAnimation.setTouchY(e.getY());
                } else if (scaledHeight < canvasHeight) {
                    zoomTo = fitScaleVertical / currentScale;
                    zoomAnimation.setTouchX(e.getX());
                    zoomAnimation.setTouchY(image.getCenterY());
                } else {
                    zoomTo = Math.min(fitScaleHorizontal, fitScaleVertical) / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(image.getCenterY());
                }
            } else {
                int scaledWidth = image.getScaledWidth();

                if (scaledWidth < canvasWidth) {
                    zoomTo = fitScaleHorizontal / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(e.getY());
                } else {
                    zoomTo = Math.min(fitScaleHorizontal, fitScaleVertical) / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(image.getCenterY());
                }
            }
        }

        zoomAnimation.setZoom(zoomTo);
        image.animationStart(zoomAnimation);
    }

    private void startZoom(MotionEvent e, float scale) {
        inZoom = true;
        zoomAnimation.reset();

        float zoomTo = 1.0f;

        if (image.isLandscape()) {
            if (image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {
                int scaledHeight = image.getScaledHeight();

                if (scaledHeight < canvasHeight) {
                    zoomTo = scale / currentScale;
                    zoomAnimation.setTouchX(e.getX());
                    zoomAnimation.setTouchY(image.getCenterY());
                } else {
                    zoomTo = scale / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(image.getCenterY());
                }
            } else {
                int scaledWidth = image.getScaledWidth();

                if (scaledWidth == canvasWidth) {
                    zoomTo = scale / currentScale;
                    zoomAnimation.setTouchX(e.getX());
                    zoomAnimation.setTouchY(e.getY());
                } else if (scaledWidth < canvasWidth) {
                    zoomTo = scale / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(e.getY());
                } else {
                    zoomTo = scale / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(image.getCenterY());
                }
            }
        } else {
            if (image.getDeviceOrientation() == Configuration.ORIENTATION_PORTRAIT) {

                int scaledHeight = image.getScaledHeight();

                if (scaledHeight == canvasHeight) {
                    zoomTo = scale / currentScale;
                    zoomAnimation.setTouchX(e.getX());
                    zoomAnimation.setTouchY(e.getY());
                } else if (scaledHeight < canvasHeight) {
                    zoomTo = scale / currentScale;
                    zoomAnimation.setTouchX(e.getX());
                    zoomAnimation.setTouchY(image.getCenterY());
                } else {
                    zoomTo = scale / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(image.getCenterY());
                }
            } else {
                int scaledWidth = image.getScaledWidth();

                if (scaledWidth < canvasWidth) {
                    zoomTo = fitScaleHorizontal / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(e.getY());
                } else {
                    zoomTo = Math.min(fitScaleHorizontal, fitScaleVertical) / currentScale;
                    zoomAnimation.setTouchX(image.getCenterX());
                    zoomAnimation.setTouchY(image.getCenterY());
                }
            }
        }

        zoomAnimation.setZoom(zoomTo);
        image.animationStart(zoomAnimation);
    }

    private void startThreeLevelZoom(MotionEvent e) {
        if(currentScale < midScale) {
            startZoom(e, midScale);
        } else if (currentScale < maxScale) {
            startZoom(e, maxScale);
        }else {
            startZoom(e, originScale);
        }
    }

    private void stopAnimations() {
        image.animationStop();
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        if (!inZoom) {

            if (!tapDetector.onTouchEvent(event)) {
                if (event.getPointerCount() == 1 && flingDetector.onTouchEvent(event)) {
                    startFling();
                }

                if (event.getAction() == MotionEvent.ACTION_UP) {
                    handleUp();
                } else if (event.getAction() == MotionEvent.ACTION_DOWN) {
                    stopAnimations();

                    last.x = event.getX();
                    last.y = event.getY();

                    if (imageListener != null) {
                        imageListener.onTouch(last.x, last.y);
                    }
                    //防止双击后触发长按
                    tapDetector.setIsLongpressEnabled(true);
                    touched = true;
                } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
                    if (event.getPointerCount() > 1) {
                        v.getParent().requestDisallowInterceptTouchEvent(true);

                        multiTouch = true;
                        if (initialDistance > 0) {

                            pinchVector.set(event);
                            pinchVector.calculateLength();

                            float distance = pinchVector.length;

                            if (initialDistance != distance) {

                                float newScale = (distance / initialDistance) * lastScale;

                                if (newScale <= maxScale) {
                                    scaleVector.length *= newScale;

                                    scaleVector.calculateEndPoint();

                                    scaleVector.length /= newScale;

                                    float newX = scaleVector.end.x;
                                    float newY = scaleVector.end.y;

                                    handleScale(newScale, newX, newY);
                                }
                            }
                        } else {
                            initialDistance = MathUtils.distance(event);

                            MathUtils.midpoint(event, midpoint);

                            scaleVector.setStart(midpoint);
                            scaleVector.setEnd(next);

                            scaleVector.calculateLength();
                            scaleVector.calculateAngle();

                            scaleVector.length /= lastScale;
                        }
                    } else {
                        if (!touched) {
                            touched = true;
                            last.x = event.getX();
                            last.y = event.getY();
                            next.x = image.getImageX();
                            next.y = image.getImageY();
                        } else if (!multiTouch) {
                            if (handleDrag(event.getX(), event.getY())) {
                                image.redraw();
                            }
                        }
                    }
                }
            }
        }

        return true;
    }

    protected void handleUp() {

        multiTouch = false;

        initialDistance = 0;
        lastScale = currentScale;

        if (!canDragX) {
            next.x = centerX;
        }

        if (!canDragY) {
            next.y = centerY;
        }

        boundCoordinates();

        if (!canDragX && !canDragY) {
            int screenOrientation = image.getDeviceOrientation();
            if (screenOrientation == Configuration.ORIENTATION_LANDSCAPE) {

                // here need to consider what's screen's direction now
                if (image.isLandscape()) {

                    currentScale = Math.min(fitScaleHorizontal, fitScaleVertical);
                    lastScale = currentScale;
                } else {
                    currentScale = fitScaleVertical;
                    lastScale = fitScaleVertical;
                }
            } else {

                if (image.isLandscape()) {

                    currentScale = fitScaleHorizontal;
                    lastScale = fitScaleHorizontal;
                } else {

                    currentScale = Math.min(fitScaleHorizontal, fitScaleVertical);
                    lastScale = currentScale;
                }
            }
        }

        image.setScale(currentScale);
        image.setPosition(next.x, next.y);

        if (imageListener != null) {
            imageListener.onScale(currentScale);
            imageListener.onPosition(next.x, next.y);
        }

        image.redraw();
    }

    protected void handleScale(float scale, float x, float y) {

        currentScale = scale;

        if (currentScale > maxScale) {
            currentScale = maxScale;
        } else if (currentScale < minScale) {
            currentScale = minScale;
        } else {
            next.x = x;
            next.y = y;
            resetToOriginX = x;
            resetToOriginY = y;
        }

        calculateBoundaries();

        image.setScale(currentScale);
        image.setPosition(next.x, next.y);

        if (imageListener != null) {
            imageListener.onScale(currentScale);
            imageListener.onPosition(next.x, next.y);
        }

        image.redraw();
    }

    protected boolean handleDrag(float x, float y) {
        current.x = x;
        current.y = y;

        float diffX = (current.x - last.x);
        float diffY = (current.y - last.y);

        if (diffX != 0 || diffY != 0) {

            if (canDragX)
                next.x += diffX;
            if (canDragY)
                next.y += diffY;

            boundCoordinates();

            if (canDragX) {
                if (diffX < 0) {//swipe from right to left
                    if (next.x == boundaryLeft) {
                        image.getParent().requestDisallowInterceptTouchEvent(false);
                        return false;
                    }
                } else { //swipe from left to right
                    if (next.x == boundaryRight) {
                        image.getParent().requestDisallowInterceptTouchEvent(false);
                        return false;
                    }
                }
                image.getParent().requestDisallowInterceptTouchEvent(true);
            } else {//not scaled, the parent can intercept the drag event
                image.getParent().requestDisallowInterceptTouchEvent(false);
            }

            last.x = current.x;
            last.y = current.y;

            if (canDragX || canDragY) {
                image.setPosition(next.x, next.y);

                if (imageListener != null) {
                    imageListener.onPosition(next.x, next.y);
                }

                return true;
            }
        }

        return false;
    }

    public void reset() {
        currentScale = startingScale;
        next.x = centerX;
        next.y = centerY;
        calculateBoundaries();
        image.setScale(currentScale);
        image.setPosition(next.x, next.y);
        image.redraw();
    }

    public float getMaxScale() {
        return maxScale;
    }

    public void setMaxScale(float maxScale) {
        this.maxScale = maxScale;
    }

    public float getMidScale() {
        return midScale;
    }

    public void setMidScale(float midScale) {
        this.midScale = midScale;
    }

    public float getMinScale() {
        return minScale;
    }

    public void setMinScale(float minScale) {
        this.minScale = minScale;
    }

    public void setOnClickListener(OnClickListener onClickListener) {
        this.onClickListener = onClickListener;
    }

    public void setOnLongClickListener(View.OnLongClickListener onLongClickListener) {
        this.onLongClickListener = onLongClickListener;
    }

    protected void setCanvasWidth(int canvasWidth) {
        this.canvasWidth = canvasWidth;
    }

    protected void setCanvasHeight(int canvasHeight) {
        this.canvasHeight = canvasHeight;
    }

    protected void setFitScaleHorizontal(float fitScale) {
        this.fitScaleHorizontal = fitScale;
    }

    protected void setFitScaleVertical(float fitScaleVertical) {
        this.fitScaleVertical = fitScaleVertical;
    }

    protected void boundCoordinates() {
        if (next.x < boundaryLeft) {
            next.x = boundaryLeft;
        } else if (next.x > boundaryRight) {
            next.x = boundaryRight;
        }

        if (next.y < boundaryTop) {
            next.y = boundaryTop;
        } else if (next.y > boundaryBottom) {
            next.y = boundaryBottom;
        }
    }

    protected void calculateBoundaries() {

        int effectiveWidth = Math.round((float) imageWidth * currentScale);
        int effectiveHeight = Math.round((float) imageHeight * currentScale);

        canDragX = effectiveWidth > displayWidth;
        canDragY = effectiveHeight > displayHeight;

        if (canDragX) {
            float diff = (float) (effectiveWidth - displayWidth) / 2.0f;
            boundaryLeft = centerX - diff;
            boundaryRight = centerX + diff;
        }

        if (canDragY) {
            float diff = (float) (effectiveHeight - displayHeight) / 2.0f;
            boundaryTop = centerY - diff;
            boundaryBottom = centerY + diff;
        }
    }

    public void setZoomType(int zoomType) {
        this.zoomType = zoomType;
    }

    public void setOriginScale(float originScale) {
        this.originScale = originScale;
    }

    public boolean isAlignLeftEdge() {
        if(currentScale == originScale) {
            return true;
        }
        if(next.x == boundaryRight) {
            return true;
        }
        return false;
    }
}
